package massobjects;

import framework.Force;
import java.awt.Dimension;
import java.awt.geom.Point2D;
import simulation.Assembly;
import simulation.Simulation;


/**
 * Regular mass objects.
 * 
 * @author David Howdyshell, Zack Hopping
 */
public class Mass extends BaseMass {
    // state
    private Force myVelocity;
    private double myMass;
    private Force myAcceleration;

    /**
     * @param id mass id
     * @param x mass x position
     * @param y mass y position
     * @param mass mass of mass
     */
    public Mass (int id, double x, double y, double mass) {
        super(id, x, y);
        myMass = mass;
        myAcceleration = new Force();
        setVelocity(0, 0);
    }

    /**
     * Adds acceleration from this time step to velocity, updates position.
     * 
     * @param sim - point to the Simulation instance
     * @param assem - pointer to assembly with this mass
     * @param dt - delta time since last update
     */
    public void update (Simulation sim, Assembly assem, double dt) {
        applyGlobalForces(sim, assem);
        // convert force back into Mover's velocity
        getVelocity().sum(myAcceleration);
        myAcceleration.reset();
        // move mass by velocity
        getCenter().setLocation(
                getCenter().getX() + myVelocity.getXChange() * dt,
                getCenter().getY() + myVelocity.getYChange() * dt);
    }

    /**
     * Applies all the global forces stored in simulation object to the mass.
     * 
     * @param sim - the simulation
     * @param assem - assembly for this mass
     */
    public void applyGlobalForces (Simulation sim, Assembly assem) {
        applyForce(sim.getForceHandler().getGravity(myMass));
        applyForce(sim.getForceHandler().getViscosityForce(myVelocity));
        applyForce(getCenterOfMassForce(getCenter(), assem.getCenterMass(), sim
                .getForceHandler().getCenterMassForce()));
        applyForce(sim.getForceHandler().getNetWallForce(getCenter()));
        applyForce(getBounce(sim.getSize(), sim.getSizeModifier()));
    }

    /**
     * Calculates the force due to the assembly's center of mass on this
     * particular mass object.
     * 
     * @param location - location of this mass
     * @param centerMass - center of mass of assembly
     * @param centerMassForce - CoM force parameters from input file
     * @return
     */
    private Force getCenterOfMassForce (Point2D location, Point2D centerMass,
            Force centerMassForce) {
        Force result = new Force(Force.angleBetween(location, centerMass),
                centerMassForce.getMagnitude());
        result.scale(1.0 / Math.pow(
                Force.distanceBetween(location, centerMass), Math
                        .abs(centerMassForce.getDirection())));
        if (centerMassForce.getDirection() >= 0) {
            result.negate();
        }
        return result;
    }

    /**
     * @param f
     *        force to be applied to this mass
     */
    public void applyForce (Force f) {
        myAcceleration.sum(f);
    }

    // check for move out of bounds
    private Force getBounce (Dimension bounds, int sizeModifier) {
        Force impulse = new Force();
        if (getLeft() < (0 - sizeModifier)) {
            impulse = new Force(0, 2);
            setCenter(-sizeModifier + getSize().width / 2, getCenter().getY());
        }
        else if (getRight() > (bounds.width + sizeModifier)) {
            impulse = new Force(180, 2);
            setCenter((bounds.width + sizeModifier) - getSize().width / 2,
                    getCenter().getY());
        }
        if (getTop() < 0) {
            impulse = new Force(270, 2);
            setCenter(getCenter().getX(), getSize().height / 2);
        }
        else if (getBottom() > bounds.height) {
            impulse = new Force(90, 2);
            setCenter(getCenter().getX(), bounds.height - getSize().height / 2);
        }
        impulse.scale(getVelocity().getRelativeMagnitude(impulse));
        return impulse;
    }

    /**
     * Returns shape's velocity.
     */
    public Force getVelocity () {
        return myVelocity;
    }

    /**
     * Resets shape's velocity.
     * 
     * @param direction - velocity angle
     * @param magnitude - velocity magnitude
     */
    public void setVelocity (double direction, double magnitude) {
        myVelocity = new Force(direction, magnitude);
    }

    @Override
    public double getMass () {
        return myMass;
    }
}
